﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;

namespace Simp.Model.CoreExpressions.Sequences
{
	/// <summary>Encapsulates an expression selecting a sub range of a sequence.</summary>
	[ExpressionName(RangeSelectorExpressionName)]
	[ParameterPolicy("source", "first", "last")]
	[DebuggerDisplay("{ExpressionHelper.Serialize(this)}")]
	public class RangeSelectorExpression : ISequenceExpression, IExpressionSerializer
	{
		#region Inner Types
		private class RangeSelectorSequenceAccessor : ISequenceAccessor
		{
			private readonly ISequenceAccessor _sourceAccessor;
			private readonly int _first;
			private readonly int _last;

			public RangeSelectorSequenceAccessor(ISequenceAccessor sourceAccessor, int first, int last)
			{
				_sourceAccessor = sourceAccessor;
				_first = first;
				_last = last;
			}

			#region ISequenceAccessor Members
			int ISequenceAccessor.Count
			{
				get { return Math.Abs(_last - _first) + 1; }
			}

			IExpression ISequenceAccessor.this[int index]
			{
				get
				{
					if (_last >= _first)
					{
						return _sourceAccessor[index + _first];
					}
					else
					{
						return _sourceAccessor[_first - index];
					}
				}
			}
			#endregion

			#region IEnumerable<IExpression> Members
			IEnumerator<IExpression> IEnumerable<IExpression>.GetEnumerator()
			{
				if (_last >= _first)
				{
					int index = 0;

					foreach (IExpression sourceElement in _sourceAccessor)
					{	//	Skip the ones before the first
						if (index >= _first)
						{
							yield return sourceElement;
						}
						++index;
						if (index > _last)
						{
							break;
						}
					}
				}
				else
				{	//	We have to buffer the sub-source range in order to reverse it
					IExpression[] buffer = new IExpression[((ISequenceAccessor)this).Count];
					int index = 0;

					foreach (IExpression sourceElement in _sourceAccessor)
					{	//	Skip the ones before the first
						if (index >= _last)
						{
							buffer[_first - index] = sourceElement;
						}
						++index;
						if (index > _first)
						{
							break;
						}
					}

					foreach (IExpression sourceElement in buffer)
					{
						yield return sourceElement;
					}
				}
			}
			#endregion

			#region IEnumerable Members
			IEnumerator IEnumerable.GetEnumerator()
			{
				return ((IEnumerable<IExpression>)this).GetEnumerator();
			}
			#endregion
		}
		#endregion

		/// <summary>Name of the expression.</summary>
		public const string RangeSelectorExpressionName = "SequenceRange";

		#region ISequenceExpression Members
		ISequenceAccessor ISequenceExpression.GetAccessor(params IExpression[] parameters)
		{
			if (parameters == null)
			{
				throw new ArgumentNullException("parameters");
			}
			if (parameters.Length == 3)
			{
				IExpression sequence = parameters[0];
				IntegerExpression first = parameters[1] as IntegerExpression;
				IntegerExpression last = parameters[2] as IntegerExpression;
				ISequenceAccessor accessor = ExpressionHelper.GetAccessor(sequence);

				if (accessor == null || first == null || last == null)
				{
					return null;
				}
				else
				{
					int count = accessor.Count;
					int firstValue = first.Value;
					int lastValue = last.Value;

					if (firstValue >= count || firstValue < -count || lastValue >= count || lastValue < -count)
					{
						return null;
					}
					else
					{
						return new RangeSelectorSequenceAccessor(
							accessor,
							firstValue >= 0 ? firstValue : count + firstValue,
							lastValue >= 0 ? lastValue : count + lastValue);
					}
				}
			}
			else
			{
				return null;
			}
		}
		#endregion

		#region IExpressionSerializer Members
		void IExpressionSerializer.Serialize(AppendOnlyStringBuilder builder, params IExpression[] parameters)
		{
			if (builder == null)
			{
				throw new ArgumentNullException("builder");
			}
			if (parameters == null)
			{
				throw new ArgumentNullException("parameters");
			}
			if (parameters.Length == 3)
			{
				IExpression sequence = parameters[0];
				IExpression first = parameters[1];
				IExpression last = parameters[2];

				ExpressionHelper.Serialize(builder, sequence);
				builder.Append("[");
				ExpressionHelper.Serialize(builder, first);
				builder.Append(":");
				ExpressionHelper.Serialize(builder, last);
				builder.Append("]");
			}
			else
			{
				ExpressionHelper.SerializeGeneric(builder, this, parameters);
			}
		}
		#endregion
	}
}